package fr.upmc.alasca.centreCalcul.interne.controleurApplication;

import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;

import fr.upmc.alasca.centreCalcul.interne.controleurAdmission.connectors.ControleurAdmissionConnector;
import fr.upmc.alasca.centreCalcul.interne.controleurAdmission.interfaces.ControleurAdmissionI;
import fr.upmc.alasca.centreCalcul.interne.controleurAdmission.ports.ControleurAdmissionOutboundPort;
import fr.upmc.alasca.centreCalcul.interne.controleurApplication.interfaces.ControleurApplicationCapteurTEI;
import fr.upmc.alasca.centreCalcul.interne.controleurApplication.interfaces.ControleurApplicationRepartitionRequeteI;
import fr.upmc.alasca.centreCalcul.interne.controleurApplication.ports.ControleurApplicationCapteurTEInboundPort;
import fr.upmc.alasca.centreCalcul.interne.controleurApplication.ports.ControleurApplicationRepartitionRequeteInboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.MachinePhysique;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.capteurCoeurs.connectors.CapteurCoeursControleurAbonnementConnector;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.capteurCoeurs.interfaces.CapteurCoeursControleurAbonnementI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.capteurCoeurs.interfaces.CapteurCoeursControleurPushI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.capteurCoeurs.ports.CapteurCoeursControleurAbonnementOutboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.capteurCoeurs.ports.CapteurCoeursControleurPushInboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.connectors.MachinePhysiqueApplicationConnector;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.capteurTempsExecution.CapteurTempsExecution;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.capteurTempsExecution.connectors.CapteurTEMachineVConnector;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.capteurTempsExecution.ports.CapteurTEMachineVOutboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.ports.MachinePhysiqueApplicationOutboundPort;
import fr.upmc.alasca.centreCalcul.objets.CoeurParam;
import fr.upmc.alasca.centreCalcul.objets.Delais;
import fr.upmc.alasca.centreCalcul.objets.DemandeChangementCoeur;
import fr.upmc.alasca.centreCalcul.objets.EtatCoeur;
import fr.upmc.alasca.centreCalcul.objets.Requete;
import fr.upmc.alasca.centreCalcul.outils.LogMessage;
import fr.upmc.components.AbstractComponent;
import fr.upmc.components.exceptions.ComponentStartException;

/**
 * Controleur d'application. <br/>
 * Chaque controleur possede une reference de l'application a superviser via
 * l'uri de l'application. Ce dernier sert d'identifiant pour recevoir des
 * informations du capteur de calcule de duree d'execution (voir
 * {@linkplain CapteurTempsExecution}).
 * 
 * Le controleur d'application est créé au niveau du controleur d'admission une fois que 
 * l'allocation de ressource a réussi.
 * @author Antoine Lavaire, Sabri Labassi, Thibaut Fleury, Ryme Fantazi
 */
public	class	ControleurApplication
extends			AbstractComponent
implements		CapteurCoeursControleurPushI
{
	/* ------------------------------------------------------------- ATTRIBUT */
	protected	boolean										estDistribue;
	protected	String										uriApplication;
	
	protected	Map<String, List<Requete>> 					requetes = new HashMap<String, List<Requete>>();
	protected	Map<String, List<Requete>> 					requetesStockage = new HashMap<String, List<Requete>>();
	protected	Map<String, List<CoeurParam>>				coeurs = new HashMap<String, List<CoeurParam>>();
	// Le délai au bout duquel le temps moyen d'exécution des requêtes est recalculé.
	protected	Delais										tempsEchantillonnage = new Delais(1, TimeUnit.SECONDS);
	// Le délai de rafraichissement de la liste des mesures.
	protected	Delais										tempsRafraichissement = new Delais(10, TimeUnit.SECONDS);
	// Durée de validité des requêtes en milisecondes.
	protected	long										dureeValiditeRequeteMS;
	protected	Delais										fourchetteMinSec;
	protected	Delais										fourchetteMaxSec;

	protected	ControleurAdmissionOutboundPort				admissionOutbound;
	
	protected	Map<String,
					MachinePhysiqueApplicationOutboundPort>	hashMapMPApplicationOutbound = new HashMap<String, MachinePhysiqueApplicationOutboundPort>();
	
	protected	Map<String, Long>							hashMapMV = new HashMap<String,Long>();
	
	// Association (uri MP -> uri MV)
	protected	Map<String, String>							hashMapMpMv = new HashMap<String, String>();
	
	protected	float										frequenceMin;
	protected	float										frequenceMax;
	
	protected	int											pourcentageSeuil = 0;
	// Statistiques sur les temps de traitement des machines virtuelles
	protected	List<String>								mvUtilisees = new ArrayList<String>();
	protected	Map<String, Map<String, Long>>			statistiquesMv = new HashMap<String, Map<String, Long>>();
	protected	String										portUri;




	/* --------------------------------------------------------- CONSTRUCTEUR */
	public	ControleurApplication
	(
		String				p_uriApplication,
		Boolean				p_estDistribue,
		Integer				p_dureeValiditeRequeteMS,
		Delais				p_fourchetteMaxSec,
		Delais				p_fourchetteMinSec,
		String				p_uriControleurAdmission,
		ArrayList<String>	p_machinesPhysiques,
		Float				p_frequenceMin,
		Float				p_frequenceMax
	) throws				Exception
	{
		// is concurrent = true, canScheculeTasck = true
		super(true, true);

		portUri = p_uriApplication + "_ControleurApplication";
		uriApplication = p_uriApplication;
		estDistribue = p_estDistribue;
		dureeValiditeRequeteMS = p_dureeValiditeRequeteMS;
		fourchetteMaxSec = p_fourchetteMaxSec;
		fourchetteMinSec = p_fourchetteMinSec;
		frequenceMax = p_frequenceMax;
		frequenceMin = p_frequenceMin;
		
		LogMessage.printMessage("Application", uriApplication,
				"Création du controleur d'application.");

		// Ajout de l'interface offerte aux capteurs de temps d'exécution.
		this.addOfferedInterface(ControleurApplicationCapteurTEI.class);
		ControleurApplicationCapteurTEInboundPort cacteip =
				new ControleurApplicationCapteurTEInboundPort(portUri + "_PushTE", this);
		this.addPort(cacteip);
		if(estDistribue)
			cacteip.publishPort();
		else
			cacteip.localPublishPort();
		
		// Ajout de l'interface inboundport pour le callback des capteurs coeurs
		this.addRequiredInterface(CapteurCoeursControleurAbonnementI.class);

		// Ajout de l'interface offerte aux coeurs (callback pour les capteurs coeurs)
		this.addOfferedInterface(CapteurCoeursControleurPushI.class);
		CapteurCoeursControleurPushInboundPort cccp =
				new CapteurCoeursControleurPushInboundPort(portUri + "_Push", this);
		this.addPort(cccp);
		if(estDistribue)
			cccp.publishPort();
		else
			cccp.localPublishPort();
		
		// Ajout de l'interface requise pour l'utilisation du contrôleur d'admission
		this.addRequiredInterface(ControleurAdmissionI.class);
		ControleurAdmissionOutboundPort caop =
				new ControleurAdmissionOutboundPort(portUri + "_Outbound", this);
		this.addPort(caop);
		caop.localPublishPort();
		caop.doConnection(p_uriControleurAdmission + "_",
				ControleurAdmissionConnector.class.getName());
		this.admissionOutbound = caop;
		
		// Ajout de l'interface offerte pour permettre au répartiteur de requête
		// de questionner le controleur d'application à propos des performances des MV
		this.addOfferedInterface(ControleurApplicationRepartitionRequeteI.class);
		ControleurApplicationRepartitionRequeteInboundPort carrip = 
				new ControleurApplicationRepartitionRequeteInboundPort(
						portUri + "_RepartitionRequete_Inbound", this);
		this.addPort(carrip);
		if(estDistribue)
			carrip.publishPort();
		else
			carrip.localPublishPort();
		
		for(String uriMP : p_machinesPhysiques) {
			// Création du port d'utilisation de la machine physique.			
			MachinePhysiqueApplicationOutboundPort mpaop =
					new MachinePhysiqueApplicationOutboundPort(uriMP + "_Outbound", this);
			this.addPort(mpaop);
			if(estDistribue)
				mpaop.publishPort();
			else
				mpaop.localPublishPort();
			mpaop.doConnection(uriMP + "_Application",
					MachinePhysiqueApplicationConnector.class.getName());
			
			synchronized(hashMapMPApplicationOutbound) {
				hashMapMPApplicationOutbound.put(uriMP, mpaop);
			}

			CapteurCoeursControleurAbonnementOutboundPort cccao =
					new CapteurCoeursControleurAbonnementOutboundPort(
							p_uriApplication + uriMP + "_CapteurCoeurControleurOutbound", this);
			this.addPort(cccao);
			if(estDistribue)
				cccao.publishPort();
			else
				cccao.localPublishPort();
			
			cccao.doConnection(uriMP + "_Capteur_Externe",
					CapteurCoeursControleurAbonnementConnector.class.getName());
			
			cccao.abonner(portUri + "_Push");
			String uriMachineVirtuelle = mpaop.recupererMV(p_uriApplication);
			
			synchronized(hashMapMV) {
				hashMapMV.put(uriMachineVirtuelle, null);
				hashMapMpMv.put(uriMachineVirtuelle, uriMP);
			}

			CapteurTEMachineVOutboundPort ctemo =
					new CapteurTEMachineVOutboundPort(
							p_uriApplication + uriMP + "_CapteurTEMachineVOutboundPort", this);
			this.addPort(ctemo);
			if(estDistribue)
				ctemo.publishPort();
			else
				ctemo.localPublishPort();
			
			ctemo.doConnection(uriMachineVirtuelle + "_CapteurTE_Externe",
					CapteurTEMachineVConnector.class.getCanonicalName());
			
			ctemo.abonnementCapteurTE(portUri);
		}
	}



	
	/* -------------------------------------------------------------- METHODE */
	@Override
	public	void				start
	() throws	ComponentStartException
	{
		super.start();

		ComponentTask rafraichisseur = new ComponentTask() {
			@Override
			public void run() {
				try {
					rafraichirListe();
				}
				catch(Exception e) {
					e.printStackTrace();
				}
			}
		};

		// On nettoie la liste à intervalle constant à partir de la fin de la
		// tache précédente, la date de nettoyage dépendra de la durée de la
		// tache. Il y a une différence par rapport au calcul du temps moyen,
		// puisqu'on veut réellement marquer une pause entre deux opérations.
		super.scheduleTaskWithFixedDelay(rafraichisseur,
				tempsRafraichissement.valeur, tempsRafraichissement.valeur,
				tempsRafraichissement.unite);
	}
	
	
	public	void				recupererMesureCapteur
	(
		Map<String, List<Requete>>	p_stockageRequete
	)
	{
		synchronized(requetesStockage) {
			requetesStockage.putAll(p_stockageRequete);
			
			synchronized(requetes) {
				for(Entry<String ,List<Requete>> entry: requetesStockage.entrySet()) {
					if(requetes.containsKey(entry.getKey()))
						requetes.get(entry.getKey()).addAll(entry.getValue());
					else
						requetes.put(entry.getKey(), entry.getValue());
				}
				
				requetesStockage.clear();
			}
		}
	}

	
	private	boolean				delaiExpire
	(
		long	p_tempsActuel,
		Requete	p_requete
	)
	{
		return p_tempsActuel - p_requete.getHeureFinTraitement() >= dureeValiditeRequeteMS;
	}
	
	
	private	void				rafraichirListe
	() throws Exception 
	{
		synchronized(requetes) {
			// On va faire de l'adaptabilité sur chaque MP/MV
			for(Entry<String,List<Requete>> requeteParMachine : requetes.entrySet()) {
				Iterator<Requete> it = requeteParMachine.getValue().iterator();
				long tempsActuel = System.currentTimeMillis();
				while(it.hasNext()) {
					Requete req = it.next();
					if(delaiExpire(tempsActuel, req))
						it.remove();
				}
				
				long somme = 0;
				int compteur = 0;
				Iterator<Requete> itr = requeteParMachine.getValue().iterator();
		
				while(itr.hasNext()) {
					Requete req = itr.next();
					somme += req.getTempsTraitement();
					compteur++;
				}
		
				long tempsMoyenExe = (compteur != 0 ? somme / compteur : 0);
				long tempsMoyenSec = tempsMoyenExe ;
				
				synchronized(hashMapMpMv){
					hashMapMV.put(requeteParMachine.getKey(), tempsMoyenSec);
					// Mise à jour des statistiques des machines virtuelles
					for(String mv : hashMapMV.keySet())
						if(!mvUtilisees.contains(mv))
							mvUtilisees.add(mv);
					statistiquesMv.put(new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(new Date()), new HashMap<String, Long>(hashMapMV));
				}
		
				synchronized(hashMapMPApplicationOutbound) {
					if(tempsMoyenSec > fourchetteMaxSec.valeur) {
						// On va donc devoir allouer ou accélérer les processeurs
						Boolean estReussiFrequence = controleSeuilInferieurFourchetteMax(tempsMoyenSec, requeteParMachine);
						controleSeuilSuperieurFourchetteMax(tempsMoyenSec, requeteParMachine, estReussiFrequence);
					}
					
					if(tempsMoyenSec < fourchetteMinSec.valeur) {
						// On va donc devoir désallouer ou ralentir les processeurs
						Boolean estReussiReduireFrequ = controleSeuilInferieurFourchetteMin(tempsMoyenSec, requeteParMachine);
						controleSeuilSuperieurFourchetteMin(tempsMoyenSec, requeteParMachine, estReussiReduireFrequ);
					}
				}
			}
		}
	}

	
	/**
	 * Permet de demander une modification (augmentation) de la fréquence d'un coeur 
	 * @param p_tempsMoyenSec
	 * @param p_requeteParMachine
	 * @return
	 * @throws Exception
	 */
	private	Boolean				controleSeuilInferieurFourchetteMax
	(
		long							p_tempsMoyenSec, 
		Entry<String, List<Requete>>	p_requeteParMachine
	) throws	Exception
	{
		Boolean estReussi = false;

		// On va donc devoir allouer ou accélérer les processeurs
		// Mise en place d'un systéme de seuil à la dure
		if(100 - ((p_tempsMoyenSec * 100) / fourchetteMaxSec.valeur) < pourcentageSeuil) {
			// On essaye d'accélérer les coeurs
			float frequenceMinCoeur = Float.MAX_VALUE;
			ArrayList<CoeurParam> listCoeurParam = new ArrayList<CoeurParam>();
			for(Entry<String, List<CoeurParam>> entryCoeurs : coeurs.entrySet()) {
				if(entryCoeurs.getKey().equals(hashMapMpMv.get(p_requeteParMachine.getKey()))) {
					// Premier passage juste pour savoir de combien on va pouvoir augmenter
					for(CoeurParam coeurParam : entryCoeurs.getValue()) {
						if(coeurParam.getFrequence() < frequenceMinCoeur)
							frequenceMinCoeur = coeurParam.getFrequence();
						if(coeurParam.getUriApplication() != null)
							if(coeurParam.getUriApplication().equals(uriApplication))
								listCoeurParam.add(coeurParam);
					}
					
					// On teste si on peut réserver la fréquence du coeur
					for(CoeurParam coeurParam : listCoeurParam) {
						if(coeurParam.getFrequence() <= (frequenceMinCoeur + MachinePhysique.differenceFrequenceCoeurs)) {
							if(coeurParam.getFrequence() + MachinePhysique.differenceFrequenceCoeurs <= this.frequenceMax) {
								if(hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).reserverNouvelleFrequence(uriApplication, coeurParam.getUriCoeur(), coeurParam.getFrequence() + MachinePhysique.differenceFrequenceCoeurs)) {
									hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).appliquerNouvelleFrequence(uriApplication);
									estReussi = true;
									break;
								}
							}
							else {
								if(this.frequenceMax != coeurParam.getFrequence()) {
									if(hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).reserverNouvelleFrequence(uriApplication, coeurParam.getUriCoeur(), this.frequenceMax)) {
										hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).appliquerNouvelleFrequence(uriApplication);
										estReussi = true;
										break;
									}
								}
							}
						}
					}
					
					listCoeurParam.clear();
					
					if(estReussi)
						break;
				}	
			}
		}
		
		if(estReussi)
			LogMessage.printMessage("Application", uriApplication,
					"A réussi à augmenter la fréquence.");
		
		return estReussi;
	}


	
	/**
	 * Permet de demander une allocation de coeurs ou bien est appelé si on n'a pas résussi d'augmenter la fréqeunce d'un ceur
	 * @param p_tempsMoyenSec
	 * @param p_requeteParMachine
	 * @param p_aReussiFrequence
	 * @return
	 * @throws Exception
	 */
	private Boolean 			controleSeuilSuperieurFourchetteMax
	(
		long							p_tempsMoyenSec, 
		Entry<String, List<Requete>>	p_requeteParMachine,
		Boolean							p_aReussiFrequence
	) throws	Exception
	{
		Boolean estReussi = false;
		
		// Si le seuil est atteind ou bien qu'on a pas réussi a appliquer une nouvelle fréquence
		if((100 - ((p_tempsMoyenSec * 100) / fourchetteMaxSec.valeur) > pourcentageSeuil) || !p_aReussiFrequence) {
			// On essaye d'allouer un coeur
			for(Entry<String, List<CoeurParam>> entryCoeurs : coeurs.entrySet()) {
				if(entryCoeurs.getKey().equals(hashMapMpMv.get(p_requeteParMachine.getKey()))) {
					for(CoeurParam coeurParam : entryCoeurs.getValue()) {
						if(coeurParam.getUriApplication() != null) {
							if(1 == hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).reserverCoeur(uriApplication, 1)) {
								hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).allouerCoeurs(uriApplication);
								estReussi = true;
								break;
							}
						}
					}
					
					if(estReussi)
						break;
				}
			}					
		}
		
		// Si on n'a pas trouvé de coeur disponible sur les MP connues
		if(!estReussi) {
			// On appelle le contrôleur d'admission pour lui demander un ajout de coeur
			admissionOutbound.ajouterCoeurs(new DemandeChangementCoeur(uriApplication, (100 - ((p_tempsMoyenSec * 100) / fourchetteMaxSec.valeur)), 1));
		}
		
		if(estReussi)
			LogMessage.printMessage("Application", uriApplication,
					"A réussi à ajouter un coeur.");
		else
			LogMessage.printMessage("Application", uriApplication,
					"N'a pas réussi à ajouter de coeur.");
		
		return estReussi;
	}


	
	/**
	 * Permet de demander une modification (diminution) de la fréquence d'un coeur
	 * @param p_tempsMoyenSec
	 * @param p_requeteParMachine
	 * @return
	 * @throws Exception
	 */
	private Boolean 			controleSeuilInferieurFourchetteMin
	(
		long							p_tempsMoyenSec, 
		Entry<String, List<Requete>>	p_requeteParMachine
	) throws Exception
	{
		Boolean aReussiReduireFrequence = false;
		
		if((100 - (((p_tempsMoyenSec * 100)) / fourchetteMinSec.valeur)) < pourcentageSeuil) {
			// On essaye d'accélérer les coeurs
			float frequenceMaxCoeur = -1;
			ArrayList<CoeurParam> listCoeurParam = new ArrayList<CoeurParam>();
			for(Entry<String, List<CoeurParam>> entryCoeurs : coeurs.entrySet()) {
				if(entryCoeurs.getKey().equals(hashMapMpMv.get(p_requeteParMachine.getKey()))) {
					// Premier passage juste pour savoir de combien on va pouvoir diminuer
					for(CoeurParam coeurParam : entryCoeurs.getValue()) {
						if(coeurParam.getFrequence() > frequenceMaxCoeur)
							frequenceMaxCoeur = coeurParam.getFrequence();
						if(coeurParam.getUriApplication() != null)
							if(coeurParam.getUriApplication().equals(uriApplication))
								listCoeurParam.add(coeurParam);
					}
					// On teste si on peut réserver la fréquence du coeur
					for(CoeurParam coeurParam : listCoeurParam) {
						if (coeurParam.getFrequence() >= (frequenceMaxCoeur - MachinePhysique.differenceFrequenceCoeurs)) {
							if(coeurParam.getFrequence() - MachinePhysique.differenceFrequenceCoeurs >= this.frequenceMin) {
								// Réservation d'une nouvelle fréquence
								MachinePhysiqueApplicationOutboundPort port =
										hashMapMPApplicationOutbound.get(entryCoeurs.getKey());
								
								boolean success = 
										port.reserverNouvelleFrequence(
												uriApplication, 
												coeurParam.getUriCoeur(), 
												coeurParam.getFrequence() - MachinePhysique.differenceFrequenceCoeurs);
								
								if(success) {
									port.appliquerNouvelleFrequence(uriApplication);
									aReussiReduireFrequence = true;
									break;
								}
							}
							else {
								if(hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).reserverNouvelleFrequence(uriApplication, coeurParam.getUriCoeur(), this.frequenceMin)) {
									hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).appliquerNouvelleFrequence(uriApplication);
									aReussiReduireFrequence = true;
									break;
								}
							}
						}
					}
					
					listCoeurParam.clear();
					if(aReussiReduireFrequence)
						break;
				}
			}	
		}
		
		if(aReussiReduireFrequence)
			LogMessage.printMessage("Application", uriApplication,
					"A réussi à réduire la fréquence.");
		
		return aReussiReduireFrequence;
	}
	
	/**
	 * Permet de relacher un coeur
	 * @param p_tempsMoyenSec
	 * @param p_requeteParMachine
	 * @param p_aReussiReduireFrequence
	 * @return
	 * @throws Exception
	 */
	private Boolean 			controleSeuilSuperieurFourchetteMin
	(
		long							p_tempsMoyenSec, 
		Entry<String, List<Requete>>	p_requeteParMachine,
		Boolean							p_aReussiReduireFrequence
	) throws Exception
	{
		if((100 - ((p_tempsMoyenSec * 100) / fourchetteMinSec.valeur) > pourcentageSeuil) || !p_aReussiReduireFrequence) {
			// On essaye de désallouer un coeur
			int coeurRestant = 0;
			for(Entry<String, List<CoeurParam>> entryCoeurs : coeurs.entrySet()) {
				if(entryCoeurs.getKey().equals(hashMapMpMv.get(p_requeteParMachine.getKey()))) {
					for(CoeurParam coeurParam : entryCoeurs.getValue()) {
						if(coeurParam.getUriApplication() != null && coeurParam.getUriApplication().equals(uriApplication)) {
							coeurRestant++;
						}
					}
				}
			}
			
			for(Entry<String, List<CoeurParam>> entryCoeurs : coeurs.entrySet()) {
				if(entryCoeurs.getKey().equals(hashMapMpMv.get(p_requeteParMachine.getKey()))) {
					for(CoeurParam coeurParam : entryCoeurs.getValue()) {
						if(coeurParam.getUriApplication() != null && coeurParam.getUriApplication().equals(uriApplication)) {
							if(coeurParam.getEtat() != EtatCoeur.disponible) {
								if(coeurRestant >= 2) {
									ArrayList<String> coeuraRelacher = new ArrayList<String>();
									coeuraRelacher.add(coeurParam.getUriCoeur());
									hashMapMPApplicationOutbound.get(entryCoeurs.getKey()).relacherCoeur(uriApplication, coeuraRelacher);
									
									LogMessage.printMessage("Application", uriApplication,
											"A réussi à désallouer un coeur.");
									
									return true;
								}
							}
						}
					}
				}
			}
		}
		
		LogMessage.printMessage("Application", uriApplication,
				"N'a pas réussi à réduire la fréquence ou à désallouer de coeur.");
		
		return false;
	}
	

	@Override
	public	void				pushCoeursConfig
	(
		List<CoeurParam>	p_coeurs,
		String				p_uriMP
	) throws	Exception
	{
		synchronized(coeurs) {
			coeurs.put(p_uriMP, p_coeurs);
		}
	}
	
	
	public	void				arreterApplication
	() throws	Exception
	{
		synchronized(hashMapMPApplicationOutbound) {
			for(Entry<String, MachinePhysiqueApplicationOutboundPort> entry : hashMapMPApplicationOutbound.entrySet()) {
				entry.getValue().shutdownApplication(uriApplication);
			}
			//hashMapMPApplicationOutbound.clear();
		}
		
		LogMessage.printMessage("Application", uriApplication,
				"Arrêt du contrôleur d'application.");

		// Création d'un fichier CSV avec les statistiques des machines virtuelles
		Double p_fourchetteMin = 0.0, p_fourchetteMax = 0.0;
		switch(fourchetteMinSec.unite) {
			case SECONDS:
				p_fourchetteMin = (double) (fourchetteMinSec.valeur * 1000);
				break;
			case MILLISECONDS:
				p_fourchetteMin = (double) fourchetteMinSec.valeur;
				break;
			case MICROSECONDS:
				p_fourchetteMin = (double) (fourchetteMinSec.valeur / 1000);
				break;
			case NANOSECONDS:
				p_fourchetteMin = (double) (fourchetteMinSec.valeur / 1000000);
				break;
			default :
				break;
		}
		switch(fourchetteMaxSec.unite) {
			case SECONDS:
				p_fourchetteMax = (double) (fourchetteMaxSec.valeur * 1000);
				break;
			case MILLISECONDS:
				p_fourchetteMax = (double) fourchetteMaxSec.valeur;
				break;
			case MICROSECONDS:
				p_fourchetteMax = (double) (fourchetteMaxSec.valeur / 1000);
				break;
			case NANOSECONDS:
				p_fourchetteMax = (double) (fourchetteMaxSec.valeur / 1000000);
				break;
			default :
				break;
		}
		
		String csvSeparator = ";";
		StringBuilder sb = new StringBuilder("Date" + csvSeparator + "Palier min" + csvSeparator + "Palier max" + csvSeparator);
		for(String mv : mvUtilisees)
			sb.append(mv + csvSeparator);
		sb.append("\n");
		
		SortedSet<String> keys = new TreeSet<String>(statistiquesMv.keySet());
		for(String key : keys) {
			sb.append(key + csvSeparator + p_fourchetteMin.toString().replaceAll("\\.", ",") + csvSeparator + p_fourchetteMax.toString().replaceAll("\\.", ",") + csvSeparator);
			for(String mv : mvUtilisees) {
				if(statistiquesMv.get(key).containsKey(mv))
					sb.append(statistiquesMv.get(key).get(mv).toString().replaceAll("\\.", ",") + csvSeparator);
				else
					sb.append(csvSeparator);
			}
			sb.append("\n");
		}
		
		try {
			File newFile = new File("log_performances" + File.separator + uriApplication + ".csv");
			if(!newFile.getParentFile().exists())
				newFile.getParentFile().mkdirs();
			newFile.createNewFile();
			Writer writer = new FileWriter(newFile);
			writer.write(sb.toString());
			writer.close();
			
			System.err.println(sb.toString());
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		
		this.shutdown();
	}


	public	Map<String, Long>	getMVStats
	() {
		return hashMapMV;
	}
	
	
	public void 				callbackAjouterCoeurs
	(
		List<String>	listMP
	) throws	Exception
	{
		LogMessage.printMessage("Application", uriApplication,
				"A reçu la réponse pour l'ajout de coeurs (" + listMP.size() + " machine(s) affectée(s)).");
		Boolean newMP = false;
		
		synchronized(hashMapMPApplicationOutbound) {
			for(String uriMachinePhysique : listMP) {
				// Si on ne connait pas encore la MP...
				if(!hashMapMPApplicationOutbound.containsKey(uriMachinePhysique)) {
					newMP = true;
					
					// On crée le port d'utilisation
					MachinePhysiqueApplicationOutboundPort mpaop =
							new MachinePhysiqueApplicationOutboundPort(
									uriMachinePhysique + "_Outbound", this);
					this.addPort(mpaop);
					if(estDistribue)
						mpaop.publishPort();
					else
						mpaop.localPublishPort();
					mpaop.doConnection(uriMachinePhysique + "_Application",
							MachinePhysiqueApplicationConnector.class.getName());
					
					hashMapMPApplicationOutbound.put(uriMachinePhysique, mpaop);
					
					
					CapteurCoeursControleurAbonnementOutboundPort cccao =
							new CapteurCoeursControleurAbonnementOutboundPort(
									uriApplication + uriMachinePhysique + "_CapteurCoeurControleurOutbound", this);
					this.addPort(cccao);
					if(estDistribue)
						cccao.publishPort();
					else
						cccao.localPublishPort();
					
					cccao.doConnection(uriMachinePhysique + "_Capteur_Externe",
							CapteurCoeursControleurAbonnementConnector.class.getName());
					
					cccao.abonner(portUri + "_Push");
					
					synchronized(hashMapMPApplicationOutbound) {
						hashMapMPApplicationOutbound.put(uriMachinePhysique, mpaop);
					}
				}
				
				
				String uriMachineVirtuelle = hashMapMPApplicationOutbound.get(uriMachinePhysique).recupererMV(uriApplication);
				
				if(newMP){
					synchronized(hashMapMV) {
						hashMapMV.put(uriMachineVirtuelle, null);
					}
					
					
					
					CapteurTEMachineVOutboundPort ctemo =
							new CapteurTEMachineVOutboundPort(
									uriApplication + uriMachinePhysique + "_CapteurTEMachineVOutboundPort", this);
					this.addPort(ctemo);
					if(estDistribue)
						ctemo.publishPort();
					else
						ctemo.localPublishPort();
					
					ctemo.doConnection(uriMachineVirtuelle + "_CapteurTE_Externe",
							CapteurTEMachineVConnector.class.getCanonicalName());
					
					ctemo.abonnementCapteurTE(portUri);
					
					synchronized(hashMapMpMv) {
						hashMapMpMv.put(uriMachineVirtuelle, uriMachinePhysique);
					}
					
					
				}
				newMP = false;
			}
		}
	}
}
